﻿using Newtonsoft.Json;
using Pfim;
using ProtoBuf;
using SharpSevenZip;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using System.Runtime.InteropServices;

namespace DHTools
{
    public class FileDataHelper
    {
        /// <summary>
        /// Writes a byte array to a file.
        /// </summary>
        public static void WriteBufferToFile(string path, byte[] buffer)
        {
            using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
            {
                fs.Write(buffer, 0, buffer.Length);
                fs.Flush();
            }
        }

        /// <summary>
        /// Reads a file into a byte array.
        /// </summary>
        public static byte[] ReadFileToBuffer(string path)
        {
            EnsureFileExists(path);

            using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                var buffer = new byte[fs.Length];
                fs.Read(buffer, 0, buffer.Length);
                return buffer;
            }
        }

        /// <summary>
        /// Ensures the file exists, throws an exception if it doesn't.
        /// </summary>
        private static void EnsureFileExists(string path)
        {
            if (!File.Exists(path))
            {
                throw new IOException("File does not exist!");
            }
        }

        /// <summary>
        /// Sets the path for the 7-Zip library.
        /// </summary>
        public static void SetSevenZipLibraryPath(string path)
        {
            SharpSevenZipBase.SetLibraryPath(path);
        }

        /// <summary>
        /// Serializes an object to a compressed byte array.
        /// </summary>
        public static byte[] SerializeObjectToBytes(object obj)
        {
            using (MemoryStream ms = new MemoryStream())
            using (MemoryStream outms = new MemoryStream())
            {
                Serializer.Serialize(ms, obj);
                var compressor = new SharpSevenZipCompressor
                {
                    ScanOnlyWritable = true,
                    CompressionLevel = CompressionLevel.Normal
                };
                compressor.CompressStream(ms, outms);
                return outms.ToArray();
            }
        }

        /// <summary>
        /// Deserializes a byte array to an object of the specified type.
        /// </summary>
        public static object DeserializeBytes(byte[] data, Type type)
        {
            using (MemoryStream ms = new MemoryStream(data))
            using (MemoryStream outms = new MemoryStream())
            {
                var decompressor = new SharpSevenZipExtractor(ms);
                decompressor.ExtractFile(0, outms);
                outms.Position = 0;
                return Serializer.Deserialize(type, outms);
            }
        }

        /// <summary>
        /// Deserializes a byte array to an object of type T.
        /// </summary>
        public static T DeserializeBytes<T>(byte[] data)
        {
            using (MemoryStream ms = new MemoryStream(data))
            using (MemoryStream outms = new MemoryStream())
            {
                var decompressor = new SharpSevenZipExtractor(ms);
                decompressor.ExtractFile(0, outms);
                outms.Position = 0;
                return Serializer.Deserialize<T>(outms);
            }
        }

        /// <summary>
        /// JSON serializer settings with indentation and reference handling.
        /// </summary>
        public static JsonSerializerSettings JsonSettings = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented,
            ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
            PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        };

        /// <summary>
        /// Loads a JSON file and deserializes it to an object of type T.
        /// </summary>
        public static T LoadJson<T>(string path)
        {
            EnsureFileExists(path);
            using (var sr = new StreamReader(path))
            {
                return JsonConvert.DeserializeObject<T>(sr.ReadToEnd(), JsonSettings);
            }
        }

        /// <summary>
        /// Serializes an object to JSON and saves it to a file.
        /// </summary>
        public static void SaveJson(string path, object config)
        {
            using (var sw = new StreamWriter(path))
            {
                sw.Write(JsonConvert.SerializeObject(config, JsonSettings));
                sw.Flush();
            }
        }

        /// <summary>
        /// Loads a Protobuf file and deserializes it to an object of type T.
        /// </summary>
        public static T LoadProtobuf<T>(string path)
        {
            EnsureFileExists(path);
            using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                var data = new byte[fs.Length];
                fs.Read(data, 0, data.Length);
                return DeserializeBytes<T>(data);
            }
        }

        /// <summary>
        /// Serializes an object to Protobuf and saves it to a file.
        /// </summary>
        public static void SaveProtobuf(string path, object config)
        {
            using (var fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
            {
                var data = SerializeObjectToBytes(config);
                fs.Write(data, 0, data.Length);
                fs.Flush();
            }
        }

        /// <summary>
        /// Extracts the file name from a full path.
        /// </summary>
        public static string ExtractFileName(string fullPath)
        {
            if (fullPath.EndsWith("\\"))
                fullPath = fullPath.Remove(fullPath.LastIndexOf("\\"));
            return fullPath.Remove(0, fullPath.LastIndexOf("\\") + 1);
        }

        /// <summary>
        /// Reads a variable-length integer (VarInt) from a binary reader.
        /// </summary>
        public static uint ReadVarInt(BinaryReader reader)
        {
            var result = 0;
            var shift = 0;

            while (shift < 32)
            {
                var b = reader.ReadByte();
                result += (b & 0x7f) << shift;

                if ((b >> 7) == 1)
                {
                    return (uint)result;
                }

                shift += 7;
            }

            throw new FormatException("VarInt is too long.");
        }

        /// <summary>
        /// Converts image data to a texture object.
        /// </summary>
        public static XTexture ConvertImageToTexture(string name, byte[] data)
        {
            var texture = new XTexture { name = name };
            var ext = Path.GetExtension(name);
            if (ext != ".DDS" && ext != ".TGA")
            {
                using (var img = Image.Load(data))
                {
                    texture.w = img.Width;
                    texture.h = img.Height;
                    texture.data = data;
                }
                return texture;
            }

            using (IImage image = Pfimage.FromStream(new MemoryStream(data)))
            {
                byte[] newData = image.Stride == image.Width * image.BitsPerPixel / 8
                    ? image.Data
                    : RemovePadding(image);

                texture.w = image.Width;
                texture.h = image.Height;
                texture.data = ConvertDdsToPng(image, newData);
                return texture;
            }
        }

        private static byte[] RemovePadding(IImage image)
        {
            var tightStride = image.Width * image.BitsPerPixel / 8;
            var newData = new byte[image.Height * tightStride];
            for (var i = 0; i < image.Height; i++)
            {
                Buffer.BlockCopy(image.Data, i * image.Stride, newData, i * tightStride, tightStride);
            }
            return newData;
        }

        private static byte[] ConvertDdsToPng(IImage image, byte[] data)
        {
            var encoder = new PngEncoder();
            using (var ms = new MemoryStream())
            {
                switch (image.Format)
                {
                    case ImageFormat.Rgba32:
                        Image.LoadPixelData<Bgra32>(data, image.Width, image.Height).Save(ms, encoder);
                        break;
                    case ImageFormat.Rgb24:
                        Image.LoadPixelData<Bgr24>(data, image.Width, image.Height).Save(ms, encoder);
                        break;
                    case ImageFormat.Rgba16:
                        Image.LoadPixelData<Bgra4444>(data, image.Width, image.Height).Save(ms, encoder);
                        break;
                    case ImageFormat.R5g5b5:
                        for (var i = 1; i < data.Length; i += 2) data[i] |= 128;
                        Image.LoadPixelData<Bgra5551>(data, image.Width, image.Height).Save(ms, encoder);
                        break;
                    case ImageFormat.R5g5b5a1:
                        Image.LoadPixelData<Bgra5551>(data, image.Width, image.Height).Save(ms, encoder);
                        break;
                    case ImageFormat.R5g6b5:
                        Image.LoadPixelData<Bgr565>(data, image.Width, image.Height).Save(ms, encoder);
                        break;
                    case ImageFormat.Rgb8:
                        Image.LoadPixelData<L8>(data, image.Width, image.Height).Save(ms, encoder);
                        break;
                    default:
                        throw new Exception($"Unsupported image format: {image.Format}");
                }
                return ms.ToArray();
            }
        }
    }
}